/*
** Text-Based Gaming System 
** Copyright (C) 2008 Eric Day
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to:
** Free Software Foundation, Inc.
** 51 Franklin Street, Fifth Floor,
** Boston, MA  02110-1301, USA
*/

#include "tgs.h"
#include "tgsd.h"

/* data file to use for puzzles */
#define TGS_SUDOKU_DATA "tgs_sudoku.data"

/* maximum number of players */
#define TGS_SUDOKU_MAX_PLAYERS 10

/* map x/y coordinates to screen coordinates */
#define TGS_SUDOKU_X(__x) (2 + 2 * (__x) + (2 * ((__x) / 3)))
#define TGS_SUDOKU_Y(__y) (1 + (__y) + ((__y) / 3))

/* struct to keep track of client game data */
typedef struct _TGS_SUDOKU
{
    char data[82];
    char player[TGS_SUDOKU_MAX_PLAYERS][TGS_NAME_LEN];
    int  blank[TGS_SUDOKU_MAX_PLAYERS];
    int  input_state;
    int  x;
    int  y;
} TGS_SUDOKU;

/* struct to keep track of server game data */
typedef struct _TGSD_SUDOKU
{
    char      data[82];
    TGSD_CON *con[TGS_SUDOKU_MAX_PLAYERS];
    int       puzzle[TGS_SUDOKU_MAX_PLAYERS][9][9];
    int       blank[TGS_SUDOKU_MAX_PLAYERS];
} TGSD_SUDOKU;

/*
** static functions
*/

/* hooks for client interface */
static int _tgs_start(TGS *tgs);
static int _tgs_part(TGS *tgs);
static int _tgs_input(TUI *tui, void *data, int key);
static int _tgs_focus(TUI *tui, void *data);
static int _tgs_data(TGS *tgs, char *buf);

/* hooks for server interface */
static int _tgsd_start(TGSD_CON *con);
static int _tgsd_join(TGSD_CON *con);
static int _tgsd_part(TGSD_CON *con);
static int _tgsd_data(TGSD_CON *con, char *buf);

/* global variables */
TGS_GAME tgs_sudoku =
{
    "Sudoku",
    _tgs_start,
    _tgs_part,
    _tgs_input,
    _tgs_focus,
    _tgs_data,
};

TGSD_GAME tgsd_sudoku =
{
    "Sudoku",
    _tgsd_start,
    _tgsd_join,
    _tgsd_part,
    _tgsd_data
};

/*
** static functions
*/

/* hooks for game interface */
static int _tgs_start(TGS *tgs)
{
    TGS_SUDOKU *s;
    int         ret;

    /* allocate and save game struct */
    s = calloc(1, sizeof(TGS_SUDOKU));
    TGS_RET_CHECK(tgs, s == NULL, "calloc", errno);

    tgs->game_data = s;

    /* setup the ui hooks for input and focus */
    tui_set_app_handlers(tgs->tui, tgs, _tgs_input, _tgs_focus);

    /* set the game status bar */
    ret = tui_set_app_status(tgs->tui,
        "Waiting For Server To Start Sudoku Game", 1);
    TGS_RET_CHECK(tgs, ret, "tui_set_app_status", ret);

    /* clean up the game screen */
    ret = werase(tgs->game_win);
    TGS_RET_CHECK(tgs, ret == ERR, "werase", -1);

    ret = wrefresh(tgs->game_win);
    TGS_RET_CHECK(tgs, ret == ERR, "wrefresh", -1);

    return 0;
}

static int _tgs_part(TGS *tgs)
{
    TGS_SUDOKU *s = (TGS_SUDOKU *)(tgs->game_data);
    int         ret;

    /* set the default status string and clear the ui hooks */
    ret = tui_set_app_status(tgs->tui, TGS_DEFAULT_GAME_STATUS, 1);
    TGS_RET_CHECK(tgs, ret, "tui_set_app_status", ret);

    tui_set_app_handlers(tgs->tui, NULL, NULL, NULL);

    /* free game struct and remove references */
    tgs->game = NULL;

    if (tgs->game_data)
    {
        free(s);
        tgs->game_data = NULL;
    }

    return 0;
}

static int _tgs_input(TUI *tui, void *data, int key)
{
    TGS        *tgs = (TGS *)data;
    TGS_SUDOKU *s = (TGS_SUDOKU *)(tgs->game_data);
    int         x;
    int         y;
    char        a[2];

    switch (s->input_state)
    {
    case 0:
        if (key == 0x08 || key == 0x7F)
        {
            /* DELETE / BACKSPACE */
            mvwaddnstr(tgs->game_win, TGS_SUDOKU_Y(s->y), TGS_SUDOKU_X(s->x),
                " ", 1);
            lpc_printf(tgs->lpc, "GAME CHANGE %x %x 0", s->x, s->y);
        }
        else if (key == 0x15)
        {
            /* CTRL-U */
            /* this will clear all cells */
            for (y = 0; y < 9; y++)
            {
                for (x = 0; x < 9; x++)
                {
                    if (s->data[(y * 9) + x] != '0')
                        continue;

                    mvwaddnstr(tgs->game_win, TGS_SUDOKU_Y(y), TGS_SUDOKU_X(x),
                        " ", 1);
                    lpc_printf(tgs->lpc, "GAME CHANGE %x %x 0", x, y);
                }
            }
        }
        else if (key == 0x1B)
        {   
            /* ESC - start escape sequence for special keys */
            s->input_state = 1;
        }
        else if (key >= 0x31 && key <= 0x39)
        {
            a[0] = key;
            a[1] = 0;
            mvwaddnstr(tgs->game_win, TGS_SUDOKU_Y(s->y), TGS_SUDOKU_X(s->x),
                a, 1);
            lpc_printf(tgs->lpc, "GAME CHANGE %x %x %c", s->x, s->y, key);
        }

        break;
    case 1:
        if (key == 0x5B)
            s->input_state = 2; /* ESC+ */
        else
            s->input_state = 0;

        break;

    case 2:
        if (key == 0x41)
        {
            /* UP ARROW */
            for (x = s->x; x >= 0; x--)
            {
                for (y = s->y - 1; y >= 0; y--)
                {
                    if (s->data[(y * 9) + x] == '0')
                    {
                        s->x = x;
                        s->y = y;
                        x = -1;
                        break;
                    }
                }
            }
        }
        else if (key == 0x42)
        {
            /* DOWN ARROW */
            for (x = s->x; x < 9; x++)
            {
                for (y = s->y + 1; y < 9; y++)
                {
                    if (s->data[(y * 9) + x] == '0')
                    {
                        s->x = x;
                        s->y = y;
                        x = 9;
                        break;
                    }
                }
            }
        }
        else if (key == 0x43)
        {
            /* RIGHT ARROW */
            for (y = s->y; y < 9; y++)
            {
                for (x = s->x + 1; x < 9; x++)
                {
                    if (s->data[(y * 9) + x] == '0')
                    {
                        s->x = x;
                        s->y = y;
                        y = 9;
                        break;
                    }
                }
            }
        }
        else if (key == 0x44)
        {
            /* LEFT ARROW */
            for (y = s->y; y >= 0; y--)
            {
                for (x = s->x - 1; x >= 0; x--)
                {
                    if (s->data[(y * 9) + x] == '0')
                    {
                        s->x = x;
                        s->y = y;
                        y = -1;
                        break;
                    }
                }
            }
        }

        s->input_state = 0;

        break;

    default:
        s->input_state = 0;

        break;
    }

    return 0;
}

static int _tgs_focus(TUI *tui, void *data)
{
    TGS        *tgs = (TGS *)data;
    TGS_SUDOKU *s = (TGS_SUDOKU *)(tgs->game_data);
    int         ret;

    /* ignore errors, this function errors when touching the last column */
    wmove(tgs->game_win, TGS_SUDOKU_Y(s->y), TGS_SUDOKU_X(s->x));

    ret = wrefresh(tgs->game_win);
    TGS_RET_CHECK(tgs, ret == ERR, "wrefresh", -1);

    return 0;
}

static int _tgs_data(TGS *tgs, char *buf)
{
    TGS_SUDOKU *s = (TGS_SUDOKU *)(tgs->game_data);
    char        player[TGS_STR_LEN];
    int         ret;
    char       *tok;
    char       *a;
    int         x;
    int         y;
    int         z;

    tok = strtok(buf, " \t");
    if (tok == NULL)
        return 0;

    if (!strcasecmp(tok, "ERR"))
    {
        tok = strtok(NULL, "");
        ret = tui_scroll_printf(tgs->tui, "*** Game Error: %s",
            tok ? tok : "-");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);

        ret = _tgs_part(tgs);
        TGS_RET_CHECK(tgs, ret, "_tgs_part", ret);
    }
    else if (!strcasecmp(tok, "INIT"))
    {
        /* grab data for new game */
        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Server Command");

        if (strlen(tok) != 81 || strspn(tok, "0123456789") != 81)
        {
            ret = tui_scroll_write(tgs->tui, "*** Invalid Server Command");
            TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
            return 0;
        }

        strcpy(s->data, tok);

        /* draw puzzle grid */
        ret = wattron(tgs->game_win, A_REVERSE);
        TGS_RET_CHECK(tgs, ret == ERR, "wattron", -1);

        for (y = 1; y <= 11; y++)
        {
            if (y % 4 == 0)
                mvwaddnstr(tgs->game_win, y, 1, "                       ", 23);
            else
            {
                mvwaddnstr(tgs->game_win, y, 8, " ", 1);
                mvwaddnstr(tgs->game_win, y, 16, " ", 1);
            }
        }

        ret = wattroff(tgs->game_win, A_REVERSE);
        TGS_RET_CHECK(tgs, ret == ERR, "wattroff", -1);

        /* fill in all the known numbers */
        ret = wattron(tgs->game_win, A_BOLD | A_UNDERLINE);
        TGS_RET_CHECK(tgs, ret == ERR, "wattron", -1);

        ret = 1;

        for (y = 0; y < 9; y++)
        {
            for (x = 0; x < 9; x++)
            {
                /* skip 0's, and find initial position */
                if (s->data[(y * 9) + x] == '0')
                {
                    if (ret)
                    {
                        s->x = x;
                        s->y = y;
                        ret = 0;
                    }

                    continue;
                }

                /* print out starting values */
                mvwaddnstr(tgs->game_win, TGS_SUDOKU_Y(y), TGS_SUDOKU_X(x),
                    s->data + (y * 9) + x, 1);
            }
        }

        mvwaddstr(tgs->game_win, 1, 30, "Blank  Player          ");

        ret = wattroff(tgs->game_win, A_BOLD | A_UNDERLINE);
        TGS_RET_CHECK(tgs, ret == ERR, "wattroff", -1);

        ret = wrefresh(tgs->game_win);
        TGS_RET_CHECK(tgs, ret == ERR, "wrefresh", -1);

        /* update the game status */
        ret = tui_set_app_status(tgs->tui, "Sudoku Game In Play", 1);
        TGS_RET_CHECK(tgs, ret, "tui_set_app_status", ret);
    }
    else if (!strcasecmp(tok, "BLANK"))
    {
        tok = strtok(NULL, " \t");
        TGS_TOK_CHECK(tgs, tok, "Invalid Server Command");

        a = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, a, "Invalid Server Command");

        x = atoi(a);

        y = -1;
        ret = 2;
        for (z = 0; z < TGS_SUDOKU_MAX_PLAYERS; z++)
        {
            if (s->player[z][0] == 0)
            {
                if (y == -1)
                    y = z;
                continue;
            }

            if (!strcasecmp(s->player[z], tok))
            {
                y = TGS_SUDOKU_MAX_PLAYERS;

                if (x < 0)
                {
                    s->player[z][0] = 0;
                    continue;
                }

                s->blank[z] = x;
            }

            snprintf(player, TGS_STR_LEN, "%5d  %-16s", s->blank[z],
                s->player[z]);
            mvwaddstr(tgs->game_win, ret, 30, player);
            ret++;
        }

        if (x >= 0 && y < TGS_SUDOKU_MAX_PLAYERS && y >= 0)
        {
            strncpy(s->player[y], tok, TGS_NAME_LEN);
            s->blank[y] = x;
            snprintf(player, TGS_STR_LEN, "%5d  %-16s", s->blank[y],
                s->player[y]);
            mvwaddstr(tgs->game_win, ret, 30, player);
            ret++;
        }

        for (; ret < TGS_SUDOKU_MAX_PLAYERS; ret++)
            mvwaddstr(tgs->game_win, ret, 30, "                       ");

        if (x == -1)
        {
            ret = tui_scroll_printf(tgs->tui, "*** %s Finished The Puzzle!",
                tok);
            TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);
        }

        ret = wattroff(tgs->game_win, A_BOLD | A_UNDERLINE);
        TGS_RET_CHECK(tgs, ret == ERR, "wattroff", -1);

        ret = wrefresh(tgs->game_win);
        TGS_RET_CHECK(tgs, ret == ERR, "wrefresh", -1);
    }
    else if (!strcasecmp(tok, "FINISH"))
    {
        ret = tui_scroll_printf(tgs->tui, "*** You Finished The Puzzle!");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);

        ret = _tgs_part(tgs);
        TGS_RET_CHECK(tgs, ret, "_tgs_part", ret);
    }
    else
    {   
        ret = tui_scroll_write(tgs->tui, "*** Invalid Server Command");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
    }

    return 0;
}

/* hooks for game interface */
static int _tgsd_start(TGSD_CON *con)
{
    TGSD_SUDOKU *s;
    FILE        *fp;
    char         buf[TGS_STR_LEN];
    int          x;
    int          ret;

    /* allocate and save game struct */
    s = calloc(1, sizeof(TGSD_SUDOKU));
    TGSD_RET_CHECK(s == NULL, "calloc", errno);

    con->gs->data = s;

    /* pick a random puzzle from the data file */
    fp = fopen(TGS_SUDOKU_DATA, "r");
    if (fp == NULL)
        fp = fopen(DATADIR "/" TGS_SUDOKU_DATA, "r");
    TGSD_RET_CHECK(fp == NULL, "fopen", errno);

    TGSD_RET_CHECK(fgets(buf, TGS_STR_LEN, fp) == NULL, "fgets", errno);

    buf[strspn(buf, "0123456789")] = 0;
    srand(time(NULL));
    x = rand() % atoi(buf);
    if (x == 0)
        x = 1;

    for(x = rand() % atoi(buf); x >= 0; x--)
        TGSD_RET_CHECK(fgets(buf, TGS_STR_LEN, fp) == NULL, "fgets", errno);

    ret = strlen(buf);
    TGSD_RET_CHECK(ret != 82, "bad data file", -1);
    buf[81] = 0;
    TGSD_RET_CHECK(ret - 1 != strspn(buf, "0123456789"), "bad data file", -1);

    fclose(fp);

    strcpy(s->data, buf);

    ret = _tgsd_join(con);
    TGSD_RET_CHECK(ret, "_tgsd_join", ret);

    return 0;
}

static int _tgsd_join(TGSD_CON *con)
{
    TGSD_SUDOKU *s = (TGSD_SUDOKU *)(con->gs->data);
    int          x;
    int          y;
    int          z;

    /* try to find an empty slot in the player list */
    for (z = 0; z < TGS_SUDOKU_MAX_PLAYERS; z++)
    {
        if (s->con[z] == NULL)
            break;
    }

    if (z == TGS_SUDOKU_MAX_PLAYERS)
        lpc_printf(con->lpc, "GAME ERR Too many players");

    s->con[z] = con;
    lpc_printf(con->lpc, "GAME INIT %s", s->data);

    s->blank[z] = 0;
    for (y = 0; y < 9; y++)
    {
        for (x = 0; x < 9; x++)
        {
            s->puzzle[z][y][x] = s->data[(y * 9) + x] - 48;

            if (s->data[(y * 9) + x] == '0')
                s->blank[z]++;
        }
    }

    x = z;

    for (z = 0; z < TGS_SUDOKU_MAX_PLAYERS; z++)
    {
        if (s->con[z] == NULL)
            continue;

        lpc_printf(s->con[z]->lpc, "GAME BLANK %s %d", con->name, s->blank[x]);
        lpc_printf(con->lpc, "GAME BLANK %s %d", s->con[z]->name, s->blank[z]);
    }

    return 0;
}

static int _tgsd_part(TGSD_CON *con)
{
    TGSD_SUDOKU *s = (TGSD_SUDOKU *)(con->gs->data);
    int          x;
    int          ref = 0;

    /* remove player from list */
    for (x = 0; x < TGS_SUDOKU_MAX_PLAYERS; x++)
    {
        if (s->con[x] == con)
            s->con[x] = NULL;

        if (s->con[x] != NULL)
        {
            lpc_printf(s->con[x]->lpc, "GAME BLANK %s -2", con->name);
            ref++;
        }
    }

    /* if no more players are left, free and remove session */
    if(!ref)
    {
        free(s);
        con->gs->game = NULL;
        con->gs->data = NULL;
    }
 
    con->gs = NULL;

    return 0;
}

static int _tgsd_data(TGSD_CON *con, char *buf)
{
    TGSD_SUDOKU *s = (TGSD_SUDOKU *)(con->gs->data);
    char        *tok;
    int          ret;
    int          val;
    int          x;
    int          y;
    int          z;
    int          chk[10];

    tok = strtok(buf, " \t");
    if (tok == NULL)
        return 0;

    if (!strcasecmp(tok, "CHANGE"))
    {
        /* update a value and see if the puzzle is solved */
        tok = strtok(NULL, " \t");
        TGSD_TOK_CHECK(con, tok, "GAME ERR Invalid GAME Command");

        x = atoi(tok);

        tok = strtok(NULL, " \t");
        TGSD_TOK_CHECK(con, tok, "GAME ERR Invalid GAME Command");

        y = atoi(tok);

        tok = strtok(NULL, "");
        TGSD_TOK_CHECK(con, tok, "GAME ERR Invalid GAME Command");

        val = atoi(tok);

        for (z = 0; z < TGS_SUDOKU_MAX_PLAYERS; z++)
        {
            if (s->con[z] == con)
                break;
        }

        if (x < 0 || x > 8 || y < 0 || y > 8 || val < 0 || val > 9 ||
            s->data[(y * 9) + x] != '0' || z == TGS_SUDOKU_MAX_PLAYERS)
        {
            lpc_printf(con->lpc, "GAME ERR Invalid GAME Command");
            return 0;
        }

        if (val && s->puzzle[z][y][x] == 0)
            s->blank[z]--;
        else if (!val && s->puzzle[z][y][x])
            s->blank[z]++;

        s->puzzle[z][y][x] = val;

        /* if there are no blank spaces, check if the puzzle is solved */
        if (s->blank[z] == 0)
        {
            for (y = 0; y < 9; y++)
            {
                /* check left to right */
                memset(chk, 0, 10 * sizeof(int));
                for (x = 0; x < 9; x++)
                    chk[s->puzzle[z][y][x]] = 1;
                for (x = 1; x < 10; x++)
                {
                    if (chk[x] == 0)
                        break;
                }

                if (x != 10)
                    break;

                /* check up and down */
                memset(chk, 0, 10 * sizeof(int));
                for (x = 0; x < 9; x++)
                    chk[s->puzzle[z][x][y]] = 1;
                for (x = 1; x < 10; x++)
                {
                    if (chk[x] == 0)
                        break;
                }

                if (x != 10)
                    break;

                /* check each box */
                memset(chk, 0, 10 * sizeof(int));
                val = (y / 3) * 3;
                ret = (y % 3) * 3;
                for (x = 0; x < 9; x++)
                    chk[s->puzzle[z][val + (x / 3)][ret + (x % 3)]] = 1;
                for (x = 1; x < 10; x++)
                {
                    if (chk[x] == 0)
                        break;
                }

                if (x != 10)
                    break;
            }

            if (y == 9)
                x = -1;
            else
                x = s->blank[z];

        }
        else
            x = s->blank[z];

        for (z = 0; z < TGS_SUDOKU_MAX_PLAYERS; z++)
        {
            if (s->con[z] != NULL && (x != -1 || s->con[z] != con))
                lpc_printf(s->con[z]->lpc, "GAME BLANK %s %d", con->name, x);
        }

        if (x == -1)
        {
            lpc_printf(con->lpc, "GAME FINISH");
            ret = _tgsd_part(con);
            TGSD_RET_CHECK(ret, "_tgsd_part", ret);
        }
    }
    else       
        lpc_printf(con->lpc, "GAME ERR Invalid GAME Command");

    return 0;
}
